package blazingbubble.game;

import blazingbubble.graphics.game.IGamePlayerActivityListener;
import moltencore.network.protocol.PlayerData;
import org.apache.log4j.Logger;
import spark.utility.Clock;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Game {
	public static enum State {
		PreparingForStart,
		AllPlayersPairedWithFields,
		Ended;
	}

	private Logger logger = Logger.getLogger(Game.class);

	private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Lock readLock = lock.readLock();
	private final Lock writeLock = lock.writeLock();

	private State state = State.PreparingForStart;

	private Integer teamSize;
	private final HashMap<Long, Field> fields = new HashMap<Long, Field>();
	private final HashMap<Long, Player> players = new HashMap<Long, Player>();
	private final HashMap<Long, Long> playerFieldPairs = new HashMap<Long, Long>();
	private HashMap<Long, Result> winMap = null;
	private boolean active = false;
	private Float timeLeft = null;
	private LinkedList<IGamePlayerActivityListener> playerActivityListeners = new LinkedList<IGamePlayerActivityListener>();

	public void addField(Field field) {
		writeLock.lock();
		try {
			fields.put(field.getFieldId(), field);
		}
		finally {
			writeLock.unlock();
		}
		updateStateIfReadyToStart();
	}

	public void addPlayer(Player player) {
		writeLock.lock();
		try {
			players.put(player.getClientId(), player);
		}
		finally {
			writeLock.unlock();
		}
		readLock.lock();
		try {
			for(IGamePlayerActivityListener playerActivityListener : playerActivityListeners) {
				playerActivityListener.playerJoined(player);
			}
		}
		finally {
			readLock.unlock();
		}
	}

	public void pairPlayerAndField(long playerId, long fieldId) {
		writeLock.lock();
		try {
			playerFieldPairs.put(playerId, fieldId);
		}
		finally {
			writeLock.unlock();
		}
		updateStateIfReadyToStart();
	}

	private void updateStateIfReadyToStart() {
		readLock.lock();
		try {
			if(!playerFieldPairs.isEmpty()) {
				// check if every player is assigned to a field
				for(Map.Entry<Long, Long> playerFieldPair : playerFieldPairs.entrySet()) {
					Long fieldId = playerFieldPair.getValue();
					Long clientId = playerFieldPair.getKey();
					Field field = fields.get(fieldId);
					// the fields must contain the field in question, else it has not been transmitted yet
					// and the game cannot start
					if(field == null) {
						return;
					}
					// if the field exists we couple it with the owner
					if(!field.getOwners().contains(players.get(clientId))) {
						field.addOwner(players.get(clientId));
					}
				}
				// being here every player has been assigned to a field because playerPaired was never false
				setState(State.AllPlayersPairedWithFields);
			}
		}
		finally {
			readLock.unlock();
		}
	}

	public Field getFieldByClientId(long clientId) {
		readLock.lock();
		try {
			return fields.get(playerFieldPairs.get(clientId));
		}
		finally {
			readLock.unlock();
		}
	}

	public Field getField(long fieldId) {
		readLock.lock();
		try {
			return fields.get(fieldId);
		}
		finally {
			readLock.unlock();
		}
	}

	public Player getPlayer(long clientId) {
		readLock.lock();
		try {
			return players.get(clientId);
		}
		finally {
			readLock.unlock();
		}
	}

	public Iterator<Field> fieldIterator() {
		//todo synchronization here for outside iteration
		return fields.values().iterator();
	}

	public Integer getFieldCount() {
		readLock.lock();
		try {
			return fields.size();
		}
		finally {
			readLock.unlock();
		}
	}

	public Integer getTeamSize() {
		return teamSize;
	}

	public void setTeamSize(Integer teamSize) {
		this.teamSize = teamSize;
	}

	public void update(float elapsedSeconds) {
		if(state != State.Ended) {
			readLock.lock();
			try {
				updateTimeLeft(elapsedSeconds);
				for(Field field : fields.values()) {
					field.update(elapsedSeconds);
				}
			}
			finally {
				readLock.unlock();
			}
		}
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	public void addPlayerActivityListener(IGamePlayerActivityListener gamePlayerActivityListener) {
		writeLock.lock();
		try {
			playerActivityListeners.add(gamePlayerActivityListener);
		}
		finally {
			writeLock.unlock();
		}
	}

	public State getState() {
		return state;
	}

	private void setState(State state) {
		this.state = state;
	}

	public Result getFieldResult(long fieldId) {
		readLock.lock();
		try {
			if(winMap != null) {
				return winMap.get(fieldId);
			}
		}
		finally {
			readLock.unlock();
		}
		return null;
	}

	public void setWinMap(HashMap<Long, Result> winMap) {
		writeLock.lock();
		try {
			this.winMap = winMap;
		}
		finally {
			writeLock.unlock();
		}
	}

	private void updateTimeLeft(float elapsedSeconds) {
		if(timeLeft != null && timeLeft > 0) {
			timeLeft -= elapsedSeconds;
		}
	}

	public void setTimeLeft(Float timeLeft) {
		writeLock.lock();
		try {
			this.timeLeft = timeLeft;
		}
		finally {
			writeLock.unlock();
		}
	}

	public Float getTimeLeft() {
		readLock.lock();
		try {
			return timeLeft;
		}
		finally {
			readLock.unlock();
		}
	}

	public void end() {
		state = State.Ended;
	}
}
